package org.nobody.mallb2c.mallware.service.impl;

import com.alibaba.fastjson.TypeReference;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import org.nobody.common.exception.NoStockException;
import org.nobody.common.to.mq.OrderTo;
import org.nobody.common.to.mq.StockDetailTo;
import org.nobody.common.to.mq.StockLockedTo;
import org.nobody.common.utils.R;
import org.nobody.mallb2c.mallware.entity.WareOrderTaskDetailEntity;
import org.nobody.mallb2c.mallware.entity.WareOrderTaskEntity;
import org.nobody.mallb2c.mallware.feign.OrderFeignService;
import org.nobody.mallb2c.mallware.feign.ProductFeignService;
import org.nobody.mallb2c.mallware.service.WareOrderTaskDetailService;
import org.nobody.mallb2c.mallware.service.WareOrderTaskService;
import org.nobody.mallb2c.mallware.vo.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.nobody.common.utils.PageUtils;
import org.nobody.common.utils.Query;

import org.nobody.mallb2c.mallware.dao.WareSkuDao;
import org.nobody.mallb2c.mallware.entity.WareSkuEntity;
import org.nobody.mallb2c.mallware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;

@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

  @Autowired
  WareSkuDao wareSkuDao;

  @Autowired
  ProductFeignService productFeignService;

  @Autowired
  RabbitTemplate rabbitTemplate;

  @Autowired
  WareOrderTaskService orderTaskService;

  @Autowired
  WareOrderTaskDetailService orderTaskDetailService;

  @Autowired
  OrderFeignService orderFeignService;

  @Data
  class SkuWareHasStock {
    private Long skuId;
    private Integer num;
    private List<Long> wareId;
  }

  @Override
  public PageUtils queryPage(Map<String, Object> params) {
    QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

    String skuId = (String) params.get("skuId");
    if (!StringUtils.isEmpty(skuId)) {
      wrapper.eq("sku_id", skuId);
    }

    String wareId = (String) params.get("wareId");
    if (!StringUtils.isEmpty(wareId)) {
      wrapper.eq("ware_id", wareId);
    }

    IPage<WareSkuEntity> page = this.page(
      new Query<WareSkuEntity>().getPage(params),
      wrapper
    );

    return new PageUtils(page);
  }

  @Override
  public void addStock(Long skuId, Long wareId, Integer skuNum) {
    //1、 判断如果还没有这个库存记录
    List<WareSkuEntity> entities = this.baseMapper.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
    if (entities == null || entities.size() == 0) {
      WareSkuEntity wareSkuEntity = new WareSkuEntity();
      wareSkuEntity.setSkuId(skuId);
      wareSkuEntity.setStock(skuNum);
      wareSkuEntity.setWareId(wareId);
      wareSkuEntity.setStockLocked(0);
      //远程查询sku的名字 如果失败 整个事务无需回滚
      //因为我们这里只是为了仓库显示时 好显示一个冗余的名称字段罢了
      try {
        R info = productFeignService.info(skuId);
        Map<String, Object> data = (Map<String, Object>) info.get("skuInfo");

        if (info.getCode() == 0) {
          wareSkuEntity.setSkuName((String) data.get("skuName"));
        }
      } catch (Exception e) {

      }
      this.baseMapper.insert(wareSkuEntity);
    } else {
      this.baseMapper.addStock(skuId, wareId, skuNum);
    }

    do {
      System.out.println(1);
    } while (2 > 1);
  }

  @Override
  public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
    return skuIds.stream().map(skuId -> {
      SkuHasStockVo vo = new SkuHasStockVo();
      //查询当前sku的总库存量
      Long count = baseMapper.getSkuStock(skuId);
      vo.setSkuId(skuId);
      vo.setHasStock(count == null ? false : count > 0);
      return vo;
    }).collect(Collectors.toList());
  }

  /**
   * 为指定订单锁定库存
   *
   * @param vo
   * @return
   * @Transactional(rollbackFor = NoStockException.class)
   * (rollbackFor = NoStockException.class)也可以不设置 因为默认任何异常都会导致回滚
   * <p>
   * 什么情况下需要解锁?
   * 1)、下订单成功。 订单过期没有支付被系统自动取消、被用户手动取消。 都要解锁库存。
   * 2)、下订单成功,库存锁定成功,但是接下来的业务调用失败,导致订单回滚,之前锁定的库存就要解锁。
   */
  @Transactional
  @Override
  public Boolean orderLockStock(WareSkuLockVo vo) {
    //todo  按照下单的收货地址,找到一个就近仓库,锁定库存

    //保存库存工作单的详情追溯
    WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
    wareOrderTaskEntity.setOrderSn(vo.getOrderSn());
    orderTaskService.save(wareOrderTaskEntity);

    //找到每个商品在哪个仓库都有库存
    List<OrderItemVo> locks = vo.getLocks();
    List<SkuWareHasStock> collect = locks.stream().map(item -> {
      SkuWareHasStock stock = new SkuWareHasStock();
      Long skuId = item.getSkuId();
      stock.setSkuId(skuId);
      stock.setNum(item.getCount());
      //查询这个商品在哪里有库存
      List<Long> wareIds = wareSkuDao.listWareIdHasSkuStock(skuId);
      stock.setWareId(wareIds);
      return stock;
    }).collect(Collectors.toList());

    //锁定库存
    for (SkuWareHasStock skuWareHasStock : collect) {
      Boolean skuStocked = false;
      Long skuId = skuWareHasStock.getSkuId();
      List<Long> wareIds = skuWareHasStock.getWareId();
      if (wareIds == null || wareIds.size() == 0) {
        throw new NoStockException(skuId);
      }

      for (Long wareId : wareIds) {
        //成功就返回1,否则就是0
        Long count = wareSkuDao.lockSkuStock(skuId, wareId, skuWareHasStock.getNum());
        if (count == 1) {
          skuStocked = true;
          //告诉MQ库存锁定成功
          //1. 如果每一个商品都锁定成功,将当前商品锁定了几件的工作单记录发给MQ
          //2. 如果锁定失败,之前保存的工作单信息就回滚了,并且此时发送出去的消息也没问题,因为工作单回滚了,于是乎在数据库中查不到相关锁定记录,也就知道数据库已经回滚了,此消息是无用的
          WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity(null, skuId, "", skuWareHasStock.getNum(), wareOrderTaskEntity.getId(), wareId, 1);
          orderTaskDetailService.save(wareOrderTaskDetailEntity);
          StockLockedTo stockLockedTo = new StockLockedTo();
          stockLockedTo.setId(wareOrderTaskEntity.getId());
          StockDetailTo stockDetailTo = new StockDetailTo();
          BeanUtils.copyProperties(wareOrderTaskDetailEntity, stockDetailTo);
          //只放id不行, 防止回滚以后找不到数据
          stockLockedTo.setDetail(stockDetailTo);
          rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", stockLockedTo);
          break;
        } else {
          //当前仓库锁失败,重试下一个仓库
        }
      }

      if (skuStocked == false) {
        // 当前商品所有仓库都没有锁住
        throw new NoStockException(skuId);
      }
    }

    //能走到这里 说明全部都锁定成功

    return true;
  }

  @Override
  public void unlockStock(StockLockedTo to) {
    System.out.println("service public void unlockStock~~~");
    StockDetailTo detail = to.getDetail();
    Long detailId = detail.getId();

    WareOrderTaskDetailEntity byId = orderTaskDetailService.getById(detailId); //mq发来实体并不一定在数据库里有(可能回滚了)
    if (byId != null) {
      //1、没有这个订单,必须解锁
      //2、有这个订单,看订单状态
      //   已取消->解锁库存
      //   没取消->不能解锁

      Long id = to.getId();
      WareOrderTaskEntity taskEntity = orderTaskService.getById(id);
      String orderSn = taskEntity.getOrderSn();
      R r = orderFeignService.getOrderStatus(orderSn);
      if (r.getCode() == 0) {
        //订单数据返回成功
        OrderVo data = r.getData(new TypeReference<OrderVo>() {
        });

        //下订单因为非库存因素而回滚了(此时库存已锁定),即没有这个订单
        // 或  订单已经被取消,即订单状态为4
        if (data == null || data.getStatus() == 4/*订单已取消、关闭*/) {
          if (byId.getLockStatus() == 1/*←已锁定*/) {
            unLockStock(detail.getSkuId(), detail.getWareId(), detail.getSkuNum(), detailId);
          }
        }

      } else { //feign调用失败, 应该再次重试解锁消息的推送
        throw new RuntimeException("unlockStock 调用远程服务失败");
      }

    } else {
      //没有查询到数据库关于这个订单的锁定库存信息(说明已经回滚了)
      //这种情况无需解锁
    }
  }

  @Transactional
  @Override
  public void unlockStock(OrderTo orderTo) {
    String orderSn = orderTo.getOrderSn();
    //查一下最新的库存状态,防止重复解锁库存
    WareOrderTaskEntity task = orderTaskService.getOrderTaskByOrderSn(orderSn);
    Long id = task.getId();

    //按照工作单找到所有没有解锁的库存,进行解锁
    List<WareOrderTaskDetailEntity> list = orderTaskDetailService.list(
      new QueryWrapper<WareOrderTaskDetailEntity>()
        .eq("task_id", id)
        .eq("lock_status", 1));

    for (WareOrderTaskDetailEntity entity : list) {
      unLockStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum(),entity.getId());
    }
  }

  private void unLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId) {
    System.out.println("service private void unLockStock~~~");
    //库存解锁
    wareSkuDao.unlockStock(skuId, wareId, num);
    //更新库存工作单的状态
    WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity();
    entity.setId(taskDetailId);
    entity.setLockStatus(2); //变为已解锁
    orderTaskDetailService.updateById(entity);
  }
}
